﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace OnlineScheduling.DomainModel
{
    /// <summary>
    /// Tu priorytetem jest iloraz Waga/Pozostały czas
    /// czyli jak będzie zadanko o wyższym tym ilorazie to wywali zadania wykonujące się,
    /// które ma ten iloram minimalny
    /// </summary>
    public class WeightBySRTSolver : Solver
    {
        public WeightBySRTSolver(Instance instance)
            : base(instance)
        { }

        protected ProcessorHeap<Processor> currentlyWorkingByFinishTime;
        protected ProcessorHeap<Processor> currentlyWorkingByMinFactor;

        public override int Run()
        {
            int weightedSum = 0;
            int time = 0;

            //kopiec z kolejno odbieranymi zadaniami, sortowany wedlug priorytetu - tutaj według wartości waga/pozostały_czas
            receivedTasksByPrioritySorted = new Heap<Task>();

            //kopiec z aktualnie wykonywanymi zadaniami, w korzeniu jest zadanie które najszybciej się zakończy
            currentlyWorkingByFinishTime = new ProcessorHeap<Processor>(processors.Count);

            //kopiec z aktualnie wykonywanymi zadaniami, w korzeniu jest zadanie i minimalnym ilorazie: waga/pozostały_czas
            currentlyWorkingByMinFactor = new ProcessorHeap<Processor>(processors.Count);


            //dopóki są nieodebrane zadania, lub zadania jeszcze nie szkończone
            //   O(N)
            while (!Instance.IsEmpty || currentlyWorkingByFinishTime.Count > 0 ||
                   receivedTasksByPrioritySorted.Count > 0)
            {

                //pierwszy krok: odpowiedź na pytanie czy wcześniej jakieś zadanie się zakończy
                //czy wcześniej przybędzie nowe zadanie do kolejki - ustalenie które zdarzenie 
                //zajdzie jako pierwsze

                //Wydarzenie: Zakończenie się aktualnie wykonywanego zadania

                int timeOfFirstFinishing = -1;
                if (currentlyWorkingByFinishTime.Count > 0)
                    timeOfFirstFinishing = currentlyWorkingByFinishTime.Root.CurrentTask.ExpectedTimeToBeDone;

                //Wydarzenie: Przyszło nowe zadanie
                int firstNewComeIn = Instance.GetTimeOfNextTask();


                int eventId = -1;
                int minimum = -1;

                if (timeOfFirstFinishing != -1)
                {
                    minimum = timeOfFirstFinishing;
                    eventId = 1;
                }
                if (firstNewComeIn != -1 && (firstNewComeIn < minimum || minimum == -1))
                {
                    minimum = firstNewComeIn;
                    eventId = 2;
                }


                switch (eventId)
                {
                    // Jakieś zadanie się zakończyło
                    case 1:
                        time = timeOfFirstFinishing;
                        while (currentlyWorkingByFinishTime.Count > 0 && currentlyWorkingByFinishTime.Root.CurrentTask.ExpectedTimeToBeDone == time)
                        {
                            // O(1) ze szczytu kopca bierzemy CPU które jako pierwsze zakończyło swoje zadanie
                            Processor processorThatFinished = currentlyWorkingByFinishTime.Pop();

                            // ten sam procesor znajduje się na drugim kopcu (gdzie ułożenie według wartości ilorazów)
                            // należy zdjąć go również z tego kopca - poszukiwanie liniowe
                            // O(m)
                            //for (int i = 0; i < currentlyWorkingByMinFactor.Count; i++)
                            //{
                                //if (currentlyWorkingByMinFactor[i] == processorThatFinished)
                                //{

                            // Dodałam do kopca możliwość wyłuskania pozycji w kopcu na podstawie
                            // oryginalnego Id:
                            // O(log m) (pobranie elementu O(1), odbudowanie kopca O(log m)
                            int indexOfProcessorInTheHeap = currentlyWorkingByMinFactor.GetHeapPositionById(processorThatFinished.Id);
                            currentlyWorkingByMinFactor.PopAtSpecificPont(indexOfProcessorInTheHeap).StopProcessingTask(time);

                                    //break;
                                //}
                            //}

                            Task finished = processorThatFinished.StopProcessingTask(time);
                            //uaktualnienie sumy ważonej
                            weightedSum += finished.Weight*finished.TimeFinished;
                            //ten procesor jest już wolny - zwróć jego id do puli wolnych IDików
                            idListOfFreeCPUs.Add(processorThatFinished.Id);
                        }
                        break;

                    // Przybyło nowe zadanie - przyjmij je do kolejki - kopca
                    case 2:
                        time = firstNewComeIn;
                        //odbierz wszystkie zadanie przybyłe w tej chwili czasu
                        while (Instance.GetTimeOfNextTask() == time)
                        {
                            Task toPut = Instance.GetNextTask();
                            toPut.PriorityType = TasksPriorityType.ByWeightSRTDivision;  //ustal priorytet - tutaj Waga/pozostały_czas
                            //wstaw na kopiec
                            receivedTasksByPrioritySorted.Push(toPut);
                        }
                        break;
                }

                //Przydzielanie zadań na wolne procesory - jeśli to możliwe
                // O(m)
                while (receivedTasksByPrioritySorted.Count > 0 && idListOfFreeCPUs.Count > 0)
                {
                    int idOfCPU = idListOfFreeCPUs.First();
                    idListOfFreeCPUs.Remove(idListOfFreeCPUs.First());  // ten id już jest zajęty

                    Task toAssign = receivedTasksByPrioritySorted.Pop();  // pobierz zadanie o największym priorytecie
                    processors[idOfCPU].AssignTask(toAssign, time);

                    currentlyWorkingByFinishTime.Push(processors[idOfCPU], processors[idOfCPU].Id);  // procesor z tym zadaniem na kopiec - wdług czasu zakończenia

                    // potrzebny jest klon procesora, aby umieścić go na drugim kopcu, zgodnie z wartościami ilorazu waga/pozostały_czas
                    Processor clonned = processors[idOfCPU].Clone() as Processor;
                    clonned.ProcessorsPriorityType = ProcessorsPriorityType.ByWSRTMinFactor; //ustalenie prioytetu
                    clonned.AssignTask(toAssign, time);
                    currentlyWorkingByMinFactor.Push(clonned, clonned.Id);
                }

                //Jeśli są zadania w kolejce a nie ma już wolnych procesorów - przerywanie zadań o mniejszym priorytecie
                // O(N)
                while (receivedTasksByPrioritySorted.Count > 0)
                {
                    bool wasPreempted = false;  //flaga, czy chociaż raz przerwano jakieś zadanie - jeśli nie, to znaczy że pozostałe mają
                    //większy priorytet niż te obecne w kolejce, można wyjść z pętli

                    //potencjalny kandydat który wywłaszczy aktualnie wykonywane zadanie
                    Processor thatWillPreemptCurrentTask = currentlyWorkingByMinFactor.Root;

                    //jeśli jego zadanie ma priorytet mniejszy od pierwszego w kolejce oczekujących
                    if (thatWillPreemptCurrentTask.CurrentTask.CompareTo(receivedTasksByPrioritySorted.Root) == -1)
                    {
                        // usuń procesor    
                        thatWillPreemptCurrentTask = currentlyWorkingByMinFactor.Pop();
                        // przerwij zadanie
                        Task toPreemt = thatWillPreemptCurrentTask.StopProcessingTask(time);
                        // oddaj zadanie do kolejki oczekujących (kopca)
                        receivedTasksByPrioritySorted.Push(toPreemt);
                        // pobierz zadanie z kopca
                        Task toAssign = receivedTasksByPrioritySorted.Pop();
                        // przydziel to zadanie na procesor
                        thatWillPreemptCurrentTask.AssignTask(toAssign, time);
                        wasPreempted = true;  // flaga - wykonano

                        // ponownie połóż procesor na kopiec
                        thatWillPreemptCurrentTask.ProcessorsPriorityType = ProcessorsPriorityType.ByWSRTMinFactor;
                        currentlyWorkingByMinFactor.Push(thatWillPreemptCurrentTask, thatWillPreemptCurrentTask.Id);

                        ////powyżej dokonano zmiany w kopcu posortowanego według ilorazu, również kopiec według 
                        //// daty zakończenia należy uaktualnić - usunąć ten procesor, i ponownie położyć sklonowany
                        ////O(m)
                        ////for (int i = 0; i < currentlyWorkingByFinishTime.Count; i++)
                        ////{
                        //    //if (currentlyWorkingByFinishTime[i] == thatWillPreemptCurrentTask)
                        //    //{

                        // Dodałam do kopca możliwość wyłuskania pozycji w kopcu na podstawie
                        // oryginalnego Id:
                        // O(log m) (pobranie elementu O(1), odbudowanie kopca O(log m)
                        int indexOfProcessorInTheHeap = currentlyWorkingByFinishTime.GetHeapPositionById(thatWillPreemptCurrentTask.Id);
                        currentlyWorkingByFinishTime.PopAtSpecificPont(indexOfProcessorInTheHeap);

                        //        //break;
                        //    //}
                        ////}

                        //klonowanie
                        Processor clonned = thatWillPreemptCurrentTask.Clone() as Processor;
                        clonned.ProcessorsPriorityType = ProcessorsPriorityType.ByEarliestFinish; //priorytet według czasów zakończenia
                        clonned.AssignTask(toAssign, time);
                        currentlyWorkingByFinishTime.Push(clonned, clonned.Id);
                    }

                    if (wasPreempted == false) break;  //jesli nie było wywłaszczeń, break
                }

            }

            WeightedSum = weightedSum;
            return WeightedSum;
        }

    }
}